package org.lemma.set;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Author;
import javax.annotation.Copyright;
import javax.annotation.Version;
import org.lemma.number.Natural;
import org.lemma.number.Numeric;

/**
 * <p>
 *  TODO: Javadoc for {@code AbstractFunction}
 * </p>
 *
 * @author Chris Beatty [christopher.beatty@gmail.com]
 * @version 1.0.0
 */
@Version (
    major="1", minor="0", patch="0", 
    date="Nov 3, 2008 2:32:58 PM",
    authors = {
        @Author (name="Chris Beatty", email="christopher.beatty@gmail.com")
    }
) @Copyright 
public abstract class      AbstractFunction<D, R, M extends Numeric>
                extends    AbstractRelation<D,R,M>
                implements Function<D,R,M> {

    /**
     * 
     * @param element
     * @return
     */
    @Override
    public M hasMember(final Tuple<D,R> element) {
        return this.evaluate(element.car()).cdr();
    }

    /**
     * 
     * @param arg
     * @return
     */
    @Override
    public FiniteSet<R,M> relate(final D arg) {
        final Function<D,R,M> f = this;
        final List<R> temp = new ArrayList<R>(1);
        temp.add(this.evaluate(arg).car());

        return new FiniteSet<R,M>() {

            @Override
            public Natural cardinality() {
                return Natural.ONE;
            }

            @Override
            public M hasMember(final R element) {
                return f.hasMember(new Tuple<D,R>(arg, element));
            }

            @Override
            public Iterator<R> iterator() {
                return temp.iterator();
            }

        };
    }

    /**
     * 
     * @param <R1>
     * @param g
     * @return
     */
    @Override
    public <R1> Function<D,R1,M> compose(final Function<R,R1,M> g) {
        if(g == null) {
            throw new IllegalArgumentException("Cannot compose a NULL");
        }
        final Function<D,R,M> f = this;

        return new AbstractFunction<D,R1,M>() {

            @Override
            public Tuple<R1,M> evaluate(D element) {
                return g.evaluate(f.evaluate(element).car());
            }

        };
    }

}
